/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/atomic.h>
#include <linux/idr.h>

#include <alga/alga.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/dp.h>
#include <alga/edid.h>
#include <alga/amd/atombios/atb.h>
#include <alga/amd/atombios/dce.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/dce6/dce6_dev.h>

#include "dce6.h"
#include "sink.h"

static ssize_t edid_read(struct file *f, struct kobject *o,
		struct bin_attribute *a, char *buf, loff_t off, size_t cnt)
{
	struct device *d;
	struct dp *dp;
	ssize_t r;

	r = 0;
	d = container_of(o, struct device, kobj);
	dp = container_of(d, struct dp, d);

	lock(dp->dce);

	if (!dp->edid)
		goto unlock;


	if (off >= dp->edid_sz)
		goto unlock;

	if (off + cnt > dp->edid_sz) {
		r = dp->edid_sz - off;
	} else
		r = cnt;

	memcpy(buf, dp->edid + off, r);

unlock:
	unlock(dp->dce);
	return r;
}

static ssize_t manufacturer_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t r;

	r = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	if (dp->edid){
		u16 manufacturer;
		u8 pnpid_char_1;
		u8 pnpid_char_2;
		u8 pnpid_char_3;

		manufacturer = alga_edid_manufacturer(dev, dp->edid);

		pnpid_char_3 = (manufacturer & 0x1f) - 1 + 'A';
		pnpid_char_2 = ((manufacturer >> 5) & 0x1f) - 1 + 'A';
		pnpid_char_1 = ((manufacturer >> 10) & 0x1f) - 1 + 'A';
		
		r = scnprintf(buf, PAGE_SIZE, "%c%c%c", pnpid_char_1,
						pnpid_char_2, pnpid_char_3);
	}

	unlock(dp->dce);
	return r;
}

static ssize_t product_code_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t r;

	r = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	if (dp->edid){
		u16 product_code;

		product_code = alga_edid_product_code(dev, dp->edid);

		r = scnprintf(buf, PAGE_SIZE, "%04X", product_code);
	}

	unlock(dp->dce);
	return r;
}

static ssize_t monitor_name_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t r;

	r = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	if (dp->edid){
		u8 name[EDID_DESCRIPTOR_STR_SZ_MAX];

		alga_edid_monitor_name(dev, dp->edid, &name[0]);

		r = scnprintf(buf, PAGE_SIZE, "%s", name);
	}

	unlock(dp->dce);
	return r;
}

static ssize_t monitor_serial_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t r;

	r = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	if (dp->edid){
		u8 serial[EDID_DESCRIPTOR_STR_SZ_MAX];

		alga_edid_monitor_serial(dev, dp->edid, &serial[0]);

		r = scnprintf(buf, PAGE_SIZE, "%s", serial);
	}

	unlock(dp->dce);
	return r;
}

static ssize_t horizontal_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t r;
	u8 *horizontal;

	r = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	if (dp->edid){
		horizontal = dp->edid + 0x15;

		r = scnprintf(buf, PAGE_SIZE, "%u", *horizontal);
	}

	unlock(dp->dce);
	return r;
}

static ssize_t vertical_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t r;
	u8 *vertical;

	r = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	if (dp->edid){
		vertical= dp->edid + 0x16;

		r = scnprintf(buf, PAGE_SIZE, "%u", *vertical);
	}

	unlock(dp->dce);
	return r;
}

/* buf max size is PAGE_SIZE */
static ssize_t modes_cpy(char *buf, struct alga_timing *t)
{
	ssize_t bytes;

	bytes = 0;

	while (t->pixel_clk) {
		ssize_t next_mode_len;

		next_mode_len = (ssize_t)strlen(t->mode) + 1;

		if (bytes + next_mode_len > PAGE_SIZE)
			goto nullify_last_linefeed;

		sprintf(buf + bytes, "%s\n", t->mode);

		bytes += next_mode_len;
		++t;
	}

nullify_last_linefeed:
	if (bytes)
		buf[bytes - 1] = '\0';
	return bytes;
}

static ssize_t pixel_fmts_cpy(char *buf, u8 *fmt)
{
	ssize_t bytes;

	bytes = 0;

	while (*fmt != ALGA_PIXEL_FMT_INVALID) {
		ssize_t next_pixel_fmt_len;

		next_pixel_fmt_len = (ssize_t)strlen(
						alga_pixel_fmts_str[*fmt]) + 1;

		if (bytes + next_pixel_fmt_len > PAGE_SIZE)
			goto nullify_last_linefeed;

		sprintf(buf + bytes, "%s\n", alga_pixel_fmts_str[*fmt]);

		bytes += next_pixel_fmt_len;
		++fmt;
	}

nullify_last_linefeed:
	if (bytes)
		buf[bytes - 1] = '\0';
	return bytes;
}

/* we something goes wrong return an empty buf */
static ssize_t modes_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t bytes;
	struct alga_timing ts[ALGA_TIMINGS_MAX];

	bytes = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	if (dp->edid){
		long r;

		r = alga_edid_timings(dp->dce->ddev.dev, dp->edid, &ts);
		if (r == -ALGA_ERR)
			goto unlock_dce;

		bytes = modes_cpy(buf, &ts[0]);
	}

unlock_dce:
	unlock(dp->dce);
	return bytes;
}

static ssize_t pixel_fmts_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;
	ssize_t bytes;
	u8 fmts[ALGA_PIXEL_FMTS_MAX];

	bytes = 0;
	dp = container_of(dev, struct dp, d);

	lock(dp->dce);

	sink_pixel_fmts(dp->dce, dp->i, &fmts);
	bytes = pixel_fmts_cpy(buf, &fmts[0]);
	
	unlock(dp->dce);
	return bytes;
}

static ssize_t block_index_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct dp *dp;

	dp = container_of(dev, struct dp, d);

	return scnprintf(buf, PAGE_SIZE, "%u", dp->i);
}

/*============================================================================*/
static struct bin_attribute bin_attr_edid = {
	.attr = {
		.name = "edid",
		.mode = S_IRUGO,
	},
	.read = edid_read,
};

/*----------------------------------------------------------------------------*/

static DEVICE_ATTR(manufacturer, S_IRUGO, manufacturer_show, NULL);
static DEVICE_ATTR(product_code, S_IRUGO, product_code_show, NULL);
static DEVICE_ATTR(monitor_name, S_IRUGO, monitor_name_show, NULL);
static DEVICE_ATTR(monitor_serial, S_IRUGO, monitor_serial_show, NULL);

static struct attribute *id_attrs[] = {
	&dev_attr_manufacturer.attr,
	&dev_attr_product_code.attr,
	&dev_attr_monitor_name.attr,
	&dev_attr_monitor_serial.attr,
	NULL
};

static struct attribute_group id_attr_group = {
	.attrs = &id_attrs[0]
};

/*----------------------------------------------------------------------------*/

static DEVICE_ATTR(block_index, S_IRUGO, block_index_show, NULL);

static struct attribute *block_index_attrs[] = {
	&dev_attr_block_index.attr,
	NULL
};

static struct attribute_group block_index_attr_group = {
	.attrs = &block_index_attrs[0]
};

/*----------------------------------------------------------------------------*/

static DEVICE_ATTR(horizontal, S_IRUGO, horizontal_show, NULL);
static DEVICE_ATTR(vertical, S_IRUGO, vertical_show, NULL);

static struct attribute *geometry_attrs[] = {
	&dev_attr_horizontal.attr,
	&dev_attr_vertical.attr,
	NULL
};

static struct attribute_group geometry_attr_group = {
	.attrs = &geometry_attrs[0]
};

/*----------------------------------------------------------------------------*/

static DEVICE_ATTR(modes, S_IRUGO, modes_show, NULL);
static DEVICE_ATTR(pixel_fmts, S_IRUGO, pixel_fmts_show, NULL);

static struct attribute *mode_attrs[] = {
	&dev_attr_modes.attr,
	&dev_attr_pixel_fmts.attr,
	NULL
};

static struct attribute_group mode_attr_group = {
	.attrs = &mode_attrs[0]
};

/*----------------------------------------------------------------------------*/

static const struct attribute_group *edid_attr_groups[] = {
	&id_attr_group,
	&block_index_attr_group,
	&geometry_attr_group,
	&mode_attr_group,
	NULL
};
/*============================================================================*/

static void dev_release_empty(struct device *dev)
{
}

long sysfs_add(struct dce6 *dce, u8 i)
{
	int id;
	struct device *d;
	long r;

	id = ida_simple_get(&ida, 0, DCE6_DISPS_MAX, GFP_KERNEL);
	if (id < 0) {
		if (id == -ENOMEM) {
			dev_err(dce->ddev.dev, "dce6:dp%u:no memory for"
							" display id\n",i);
		} else if (id == -ENOSPC) {
			dev_err(dce->ddev.dev, "dce6:dp%u:no more room for new"
							" display id\n",i);
		} else {
			dev_err(dce->ddev.dev, "dce6:dp%u:display id allocation"
								" failed\n",i);
		}
		goto err;
	}
	dce->dps[i].id=(unsigned int)id;

	d = &dce->dps[i].d;
	memset(d, 0, sizeof(*d));

	d->class = class;
	d->parent = dce->parent_char_dev;
	d->release = dev_release_empty;

	sysfs_attr_init(&dev_attr_manufacturer.attr);
	sysfs_attr_init(&dev_attr_product_code.attr);
	sysfs_attr_init(&dev_attr_monitor_name.attr);
	sysfs_attr_init(&dev_attr_monitor_serial.attr);
	d->groups = &edid_attr_groups[0];

	r = dev_set_name(d, "dce6_display%u", id);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce6:dp%u:unable to set display device"
								" name\n",i);
		goto err_put_dev;
	}

	r = device_register(d);
	if (r != 0) {
		dev_err(dce->ddev.dev, "dce6:dp%u:unable to register display"
								" device\n",i);
		goto err_put_dev;
	}

	sysfs_bin_attr_init(&bin_attr_edid);
	r = device_create_bin_file(d, &bin_attr_edid);
	if (r != 0)
		goto err_unregister_dev;
	return 0;

err_unregister_dev:
	device_unregister(d);
	goto err_remove_id;

err_put_dev:
	put_device(d);

err_remove_id:
	ida_simple_remove(&ida, dce->dps[i].id);
err:
	return -DCE6_ERR;
}

void sysfs_remove(struct dce6 *dce, u8 i)
{
	ida_simple_remove(&ida, dce->dps[i].id);
	device_remove_bin_file(&dce->dps[i].d, &bin_attr_edid);
	device_unregister(&dce->dps[i].d);
}
